/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.inria.openmath.omapi.implementation;

/**
 *
 * @author Mr.Sam
 */
import java.io.*;
import java.math.BigInteger;
import java.util.Vector;
import fr.inria.openmath.omapi.*;

public class TreeParserHandler extends ParserAdapter {

    // must we trace context push/pop/set... who said it was for debug?
    private static final boolean traceContextStack = true;

    // size of context stack at creattion
    private static final int CONTEXT_STACK_INIT_SZ = 64;

    // size multiplicator for context stack (when stack full)
    private static final int CONTEXT_STACK_MUL_SZ = 2;

    // size of node stack at creattion
    private static final int NODE_STACK_INIT_SZ = 64;

    // size multiplicator for node stack (when stack full)
    private static final int NODE_STACK_MUL_SZ = 2;

    // all parsing contexts stored in context stack
    private static final byte OMC_UNKNOWN = 0;

    private static final byte OMC_OBJ = 1;

    private static final byte OMC_ATTR = 2;

    private static final byte OMC_ATPS = 3;

    private static final byte OMC_ATPO = 4;

    private static final byte OMC_APPS = 5;

    private static final byte OMC_APPO = 6;

    private static final byte OMC_ERRS = 7;

    private static final byte OMC_ERRO = 8;

    private static final byte OMC_BINDB = 9;

    private static final byte OMC_BINDV = 10;

    private static final byte OMC_BINDO = 11;

    Node root, current;

    Locator locator;

    // some comments are found before the first 'real' OpenMath node
    // must store them to set them on a node later
    String danglingComment;

    // some PIs are found before the first 'real' OpenMath node
    // must store them to set them on a node later
    Vector danglingPis;

    // each time a tree event occurs (eg: a new tree has been build) 
    // a function of this handler is called.
    TreeHandler treeHandler;

    // parsing context stack (used to do syntax checking or attribute collecting)
    byte contextStack[];

    // position of last cell in stack (0 is first, -1 is empty)
    int contextStackHeadPos = -1;

    // stack of nodes used mainly as attributes name/value temp storage
    Node nodeStack[];

    // position of last cell in stack (0 is first, -1 is empty)
    int nodeStackHeadPos = -1;


    private void throwOME(String message, Exception e) throws OMException {
        if (locator != null) {
            throw new OMException(
                    message + " (at line:" + locator.getLineNumber() + " col:"
                    + locator.getColumnNumber() + ")", e);
        } else {
            throw new OMException(message, e);
        }
    }

    private void throwOME(String message, short code) throws OMException {
        if (locator != null) {
            throw new OMException(
                    message + " (at line:" + locator.getLineNumber() + " col:"
                    + locator.getColumnNumber() + ")", code);
        } else {
            throw new OMException(message, code);
        }

    }

    private void throwOME(String message) throws OMException {
        throwOME(message, OMException.OME_GENERIC);
    }


    private String contextToString(byte context) {
        switch (context) {
            case OMC_OBJ: 
                return "obj";
            case OMC_ATTR: 
                return "attr";
            case OMC_ATPS: 
                return "atps";
            case OMC_ATPO: 
                return "atpo";
            case OMC_APPS: 
                return "apps";
            case OMC_APPO: 
                return "appo";
            case OMC_ERRS: 
                return "errs";
            case OMC_ERRO: 
                return "erro";
            case OMC_BINDB: 
                return "bbin";
            case OMC_BINDV: 
                return "bvar";
            case OMC_BINDO: 
                return "bobj";
            case OMC_UNKNOWN: default: 
                return "unknown";
        }
    }

    private void pushContext(byte context) throws OMException {
        int i;
        byte newStack[];

        contextStackHeadPos++;
        if (contextStack == null)
            contextStack = new byte[CONTEXT_STACK_INIT_SZ];
        else if (contextStack.length == contextStackHeadPos) {
            newStack = new byte[contextStack.length * CONTEXT_STACK_MUL_SZ];
            for (i = 0; i < contextStack.length; i++)
                newStack[i] = contextStack[i];
            contextStack = newStack;
        }
        contextStack[contextStackHeadPos] = context;
        if (traceContextStack) {
            System.out.println("-->" + contextToString(context));
        }
    }

    private byte popContext() throws OMException {
        if (contextStackHeadPos < 0) {
            throwOME
                    (
                    "tree builder context stack is empty. this probably means a syntax error."
                    , OMException.OME_INTERNAL);
        }
        if (traceContextStack) {
            System.out.println("<--" + contextToString(
                    contextStack[contextStackHeadPos]));
        }

        return contextStack[contextStackHeadPos--];
    }

    private byte popContext(byte check) throws OMException {
        checkContext(check);
        return popContext();
    }

    private byte getContext() throws OMException {
        try {
            return contextStack[contextStackHeadPos];
        } catch (Exception e) {
            throwOME
                    (
                    "tree builder context stack is empty. this probably means a syntax error."
                    , OMException.OME_INTERNAL);
            return 0;                  // fool this barindead compiler
        }
    }

    private void setContext(byte context) throws OMException {
        if (traceContextStack) {
            System.out.println("-=-" + contextToString(context));
        }
        try {
            contextStack[contextStackHeadPos] = context;
        } catch (Exception e) {
            throwOME
                    (
                    "tree builder context stack is empty. this probably means a syntax error."
                    , OMException.OME_INTERNAL);
        }
    }

    private void checkContext(byte check) throws OMException {
        if (getContext() != check) {
            throwOME
                    (
                    "tree builder found a syntax error (was looking for a context '"
                    + contextToString(check) + "' and found '" +
                    contextToString(getContext()) + "').",
                    OMException.OME_SYNTAX);
        }
    }


    private void pushNode(Node node) throws OMException {
        int i;
        Node newStack[];

        nodeStackHeadPos++;
        if (nodeStack == null)
            nodeStack = new Node[NODE_STACK_INIT_SZ];
        else if (nodeStack.length == nodeStackHeadPos) {
            newStack = new Node[nodeStack.length * NODE_STACK_MUL_SZ];
            for (i = 0; i < nodeStack.length; i++)
                newStack[i] = nodeStack[i];
            nodeStack = newStack;
        }
        nodeStack[nodeStackHeadPos] = node;

    }

    private Node popNode() throws OMException {
        if (nodeStackHeadPos < 0) {
            throwOME("tree builder node stack is empty",
                    OMException.OME_INTERNAL);
        }
        return nodeStack[nodeStackHeadPos--];
    }

    private Node getNode() throws OMException {
        return nodeStack[nodeStackHeadPos];
    }

    private void checkLegalContext(short nodeType) throws OMException {

        switch (getContext()) {
            case OMC_ATPS: 
                if (nodeType != Node.OM_SYMBOL)
                    // OpenMath syntax says this node should be a Symbol
                    // thus this is a syntax error
                    throwOME
                            (
                            "malformed attribute. (first pair element must be a symbol)"
                            , OMException.OME_SYNTAX);
                break;
            case OMC_APPS: 
                if (nodeType != Node.OM_SYMBOL)
                    // OpenMath syntax says this node should be a Symbol
                    // thus this is a syntax error
                    throwOME
                            (
                            "malformed application. (first element must be a symbol)"
                            , OMException.OME_SYNTAX);
                break;
            case OMC_ERRS: 
                if (nodeType != Node.OM_SYMBOL)
                    // OpenMath syntax says this node should be a SymbolkStnbol
                    // thus this is a syntax error
                    throwOME("malformed error. (first element must be a symbol)"
                            , OMException.OME_SYNTAX);
                break;
            case OMC_BINDV: 
                if (nodeType != Node.OM_VAR)
                    // OpenMath syntax says this node should be a Variable
                    // thus this is a syntax error
                    throwOME("found unlegal node in bound variable list.",
                            OMException.OME_SYNTAX);
                break;
        }
    }

    private boolean processAttributeValue() throws OMException {
        Node parent;

        if (getContext() != OMC_ATPO) {
            return false;
        } else {
            // this node is the value of an attribute
            // keep it to put on forthcomming attributed object
            // and remove it from global tree
            parent = current.getParent();
            parent.removeChild(current);
            pushNode(current);
            current = parent;
            setContext(OMC_ATPS);
            return true;
        }
    }

    private void insertLeaf(Node leaf) throws OMException {
        if (getContext() != OMC_ATPO) {
            // this is a plain node
            current.appendChild(leaf);
        } else {
            // this is the value in an attribute pair
            pushNode(leaf);
            setContext(OMC_ATPS);
        }
    }


    /**
     * Set the TreeHandler.
     *
     * <p>Tree events (like "tree parsed") will be delivered to this TreeHandler.
     * @params treeHandler the new tree event handler to use.
     */
    public void setTreeHandler(TreeHandler treeHandler) {
        this.treeHandler = treeHandler;
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#setLocator
     */
    public void setLocator(Locator locator) {
        this.locator = locator;
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startParse
     */
    public void startParse() {
        current = root = null;
        danglingComment = null;
        danglingPis = null;
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startObject
     */
    public void startObject() throws OMException {
        if (root != null) {
            throwOME("wrong OMOBJ imbrication.");
        }
        root = new NodeImpl(Node.OM_OBJECT);
        if (danglingComment != null) {
            root.setComments(danglingComment);
            danglingComment = null;
        }
        if (danglingPis != null) {
            root.setPInstructions(danglingPis);
            danglingPis = null;
        }
        current = root;
        pushContext(OMC_OBJ);
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#endObject
     */
    public void endObject() throws OMException {
        if (current != root) {
            throwOME("object was not complete.", OMException.OME_SYNTAX);
        }
        popContext(OMC_OBJ);
        if (treeHandler != null) {
            treeHandler.parsedTree(root);
        }
        root = current = null;
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startApplication
     */
    public void startApplication() throws OMException {
        NodeImpl n;
        checkLegalContext(Node.OM_APP);
        pushContext(OMC_APPS);
        n = new ApplyNodeImpl();
        current.appendChild(n);
        current = n;
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#endApplication
     */
    public void endApplication() throws OMException {
        popContext(OMC_APPO);
        if (!processAttributeValue()) {
            // normal case... this is a plain node in global tree
            current = (NodeImpl) current.getParent();
        }
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startAttribution
     */
    public void startAttribution() throws OMException {
        pushContext(OMC_ATTR);
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#endAttribution
     */
    public void endAttribution() throws OMException {
        Node value, name;

        popContext(OMC_ATTR);
        while ((value = popNode()) != null) {
            name = popNode();
            (current.getLastChild()).setAttribute(
                    ((SymbolNode) name).getValue(), value);
        }
    // FIXME add check for more than one attributed object
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startAttributePairs
     */
    public void startAttributePairs() throws OMException {
        checkContext(OMC_ATTR);
        pushNode(null);
        pushContext(OMC_ATPS);
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#endAttributePairs
     */
    public void endAttributePairs() throws OMException {
        popContext(OMC_ATPS);
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startBind
     */
    public void startBind() throws OMException {
        NodeImpl n;
        checkLegalContext(Node.OM_BIND);
        pushContext(OMC_BINDB);
        n = new BindNodeImpl();
        current.appendChild(n);
        current = n;
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#endBind
     */
    public void endBind() throws OMException {
        Node n, b;

        popContext(OMC_BINDO);
        if (!processAttributeValue()) {
            // normal case... this is a plain node in global tree
            // plug the last node (bound object) at 2nd place in list of child of BindNode.
            n = current.getLastChild();
            b = current.getFirstChild();
            b = b.getNextSibling();
            current.insertBefore(n, b);

            current = (NodeImpl) current.getParent();
        }
    // FIXME add check for more than one bound object
    }



    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startBVars
     */
    public void startBVars() throws OMException {
        checkContext(OMC_BINDB);
        pushContext(OMC_BINDV);
    // FIXME add check for more than one binder
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#endBVars
     */
    public void endBVars() throws OMException {
        popContext(OMC_BINDV);
        setContext(OMC_BINDO);
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#startError
     */
    public void startError() throws OMException {
        NodeImpl n;

        checkLegalContext(Node.OM_ERROR);
        pushContext(OMC_ERRS);
        n = new NodeImpl(Node.OM_ERROR);
        current.appendChild(n);
        current = n;
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#endError
     */
    public void endError() throws OMException {
        popContext(OMC_ERRO);
        if (!processAttributeValue()) {
            // normal case... this is a plain node in global tree
            current = (NodeImpl) current.getParent();
        }
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readInteger
     */
    public void readInteger(int value) throws OMException {
        checkLegalContext(Node.OM_INT32);
        insertLeaf(new IntNodeImpl(value));
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readInteger
     */
    public void readInteger(BigInteger value) throws OMException {
        checkLegalContext(Node.OM_BIGINT);
        insertLeaf(new BigIntegerNodeImpl(value));
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readFloat
     */
    public void readFloat(double value) throws OMException {
        checkLegalContext(Node.OM_FLOAT64);
        insertLeaf(new FloatNodeImpl(value));
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readString
     */
    public void readString(String value) throws OMException {
        checkLegalContext(Node.OM_STRING);
        insertLeaf(new StringNodeImpl(value));
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readVariable
     */
    public void readVariable(String name) throws OMException {
        checkLegalContext(Node.OM_VAR);
        insertLeaf(new VariableNodeImpl(name));
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readByteArray
     */
    public void readByteArray(byte[] value) throws OMException {
        checkLegalContext(Node.OM_BYTEARRAY);
        insertLeaf(new ByteArrayNodeImpl(value));
    }


    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readSymbol
     */
    public void readSymbol(Symbol value) throws OMException {
        checkLegalContext(Node.OM_SYMBOL);
        Node n;
        n = new SymbolNodeImpl(value);
        switch (getContext()) {
            case OMC_ATPS: 
                // ok found the name (symbol) for this attribute pair
                pushNode(n);
                setContext(OMC_ATPO);
                break;
            case OMC_ATPO: 
                // this symbol is the value of this attribute pair
                pushNode(n);
                setContext(OMC_ATPS);
                break;
            case OMC_APPS: 
                // ok found the symbol for this application 
                current.appendChild(n);
                setContext(OMC_APPO);

                break;
            case OMC_ERRS: 
                // ok found the symbol for this error  
                current.appendChild(n);
                setContext(OMC_ERRO);
                break;
            default: 
                // normal case... this is a plain node in global tree
                current.appendChild(n);
        }
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readComment
     */
    public void readComment(String comment) throws OMException {
        String oldComments;
        Node node;

        if (current == null) {
            // this is a dangling comment
            // store it to add it to root
            if (danglingComment == null)
                danglingComment = comment;
            else
                danglingComment = danglingComment + "\n" + comment;
        } else {
            if ((node = current.getLastChild()) == null) {
                node = current;
            }
            oldComments = node.getComments();
            if (comment != null) {
                if (oldComments == null) {
                    node.setComments(comment);
                } else {
                    node.setComments(oldComments + "\n" + comment);
                }
            }
        }
    }

    /**
     * @see fr.inria.openmath.omapi.ParserHandler#readComment
     */
    public void readPInstruction(String pi) throws OMException {
        Vector pis;
        Node node;

        if (current == null) {
            // this is a dangling pi
            // store it to add it later to root (next node)
            if (danglingPis == null) {
                danglingPis = new Vector();
            }
            danglingPis.addElement(pi);
        } else {
            if ((node = current.getLastChild()) == null) {
                node = current;
            }
            if (pi != null) {
                pis = node.getPInstructions();
                if (pis == null) {
                    pis = new Vector();
                    node.setPInstructions(pis);
                }
                pis.addElement(pi);
            }
        }
    }

}
